#####################################################################
#	$Id: File.pm,v 1.1.1.1 2006/03/20 04:08:45 jfb Exp $
#
# File.pm - class for handling Epiphyte template pages (.cnp files)
#
#
#####################################################################

package Epiphyte::File;

use strict;
use vars qw($VERSION @ISA $CSSINC);

use Data::Dumper;

use HTTP::Request;
use HTTP::Request::Params;

use IO::File;
use IO::Handle;

use Epiphyte::Log;
use Epiphyte::Forum;
use Epiphyte::Thread;

use constant FILE_SECTION_NONE	=> 0;
use constant FILE_SECTION_HDR	=> 1;
use constant FILE_SECTION_BODY	=> 2;


my %keywords = (
	'body'		=>	\&parse_body,
	'else'		=>	\&parse_cond,
	'endif'		=>	\&parse_cond,
	'eval'		=>	\&parse_eval,
	'head'		=>	\&parse_head,
	'if'		=>	\&parse_cond,
	'include'	=>	\&parse_include,
	'title'		=>	\&parse_title,
);


@ISA = qw(Exporter);

$CSSINC = "/css/main.css";


my %FILE_CACHE = ();



#####################################################################   
# new - class method
#
# Create a new species object
#####################################################################

sub new
{
	my ($class, $path) = @_;

	my $self = {
		_path	=> $path,
		_sect	=> FILE_SECTION_NONE,
		_htver	=> '4.01 Transitional',
		_lang	=> 'EN',
	};

	bless($self, $class);

	return ($self);
}

#####################################################################   
# load - class method
#
# Load a Epiphyte (.cnp) File from the path $path.
#####################################################################

sub load
{
	my ($class, $path, $level) = @_;

	my $base = Epiphyte->getvar('BASEDIR');

	my $self = {
		_path	=> $base . $path,
		_sect	=> FILE_SECTION_NONE,
		_htver	=> '4.01 Transitional',
		_lang	=> 'EN',
		_level	=> $level,
	};

	if ($level == 0) {
		$self->{_out} = <<EOB;
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML $self->{_htver}//$self->{_lang}">
EOB
	}

	bless($self, $class);

	# Stick ourself in the cache
	$FILE_CACHE{$path} = $self;

	return ($self);
}

#####################################################################   
# output - object method
#
# Return the output generated by the file.
#####################################################################

sub output
{
	my ($self) = @_;

	return ($self->{_out});
}

#####################################################################   
# parse - object method
#
# Parse a Epiphyte template file and generate the HTML.
#####################################################################

sub parse
{
	my ($self, $req) = @_;
	my $class = ref($self);

	print "Opening $self->{_path}\n";
	my $fh = new IO::File($self->{_path}, O_RDONLY);
	if (!$fh) {
		Epiphyte->log(LOG_ERR, "failed to open $self->{_path}: $!");
		return;
	}

	$self->{_fh} = $fh;
	$self->{_req} = $req;
	$self->{_lineno} = 0;
	$self->{_out} = "<html>\n";

	while (my $line = $fh->getline()) {
		$self->{_lineno}++;

		if ($line =~ /^\s*#/) {
			# Comment line, skip
			next;
		} elsif ($line =~ /^%(\w+)\s+(.*)$/) {
			foreach(%keywords) {
				if ($_ eq $1) {
					if (!$keywords{$_}->($self, $2)) {
						return;
					}
					last;
				}
			}
		} elsif ($line =~ /^$/) {
			if ($self->{_sect} == FILE_SECTION_BODY) {
				# Translate blank lines to break rules
				$self->{_out} .= "<br>\n"
			}	
		} else {
			$self->{_out} .= $line;
		}
	}

	$fh->close();
	$self->{_fh} = undef;
	$self->{_lineno} = 0;

	return ($self->{_out});
}

#####################################################################   
# parse_body - object method
#
#####################################################################

sub parse_body
{
	my ($self, $line) = @_;

	if ($self->{_sect} == FILE_SECTION_BODY) {
		$self->error("duplicate `%body' tag in page");
		return;
	}

	$self->{_out} .= "</head>\n" if ($self->{_sect} == FILE_SECTION_HDR);

	$self->{_sect} = FILE_SECTION_BODY;
	$self->{_out} .= "<body>\n";
	return (1);
}

#####################################################################   
# parse_head - object method
#
#####################################################################

sub parse_head
{
	my ($self, $line) = @_;
	my $css = $CSSINC;

	if ($self->{_sect} >= FILE_SECTION_HDR) {
		$self->error("misplaced `%head' tag in page");
		return;
	}


	$self->{_sect} = FILE_SECTION_HDR;
	$self->{_out} .= <<EOH;
<head>
<link rel=StyleSheet href="$css" type="text/css" media="screen">
EOH

	return (1);
}

#####################################################################   
# parse_title - object method
#
#####################################################################

sub parse_title
{
	my ($self, $line) = @_;

	if ($self->{_sect} != FILE_SECTION_HDR) {
		$self->error("`%title' tag used outside of file header");
		return;
	}

	$self->{_out} .= "<title>$line</title>\n";

	return (1);
}

#####################################################################   
# parse_eval - object method
#
# Parse an %eval block.
#####################################################################

sub parse_eval
{
	my ($self, $line) = @_;
	my $block = $line;

	while (my $l = $self->{_fh}->getline()) {
		$block .= $l;
		last if ($l =~ /^}$/);
	}

	my $out = $self->eval($block);
	return if (!defined($out));

	$self->{_out} .= $out;

	return (1);
}

#####################################################################   
# parse_include - object method
#
# Parse an %include tag.
#####################################################################

sub parse_include
{
	my ($self, $line) = @_;
	print "Include line: $line\n";

	my $inc = Epiphyte::File->load($line, $self->{_level} + 1);
	return if (!$inc);

	# Inherit the current context
	$inc->{_sect} = $self->{_sect};

	if (!$inc->parse($self->{_req})) {
		$self->error("failed to include $line");
		return;
	}

	$self->{_out} .= $inc->{_out};

	return (1);
}


#####################################################################   
# parse_cond - object method
#
# Parse one of the conditional keywords (if, else, endif).
#####################################################################

sub parse_cond
{
	my ($self, $line) = @_;

	return (1);
}

#####################################################################   
# parse_end - object method
#
# Parse the %end tag, which causes all further output to end here.
#####################################################################

sub parse_end
{
	my ($self, $line) = @_;

	$self->{_out} .= "</body>\n" if ($self->{_sect} == FILE_SECTION_BODY);
	$self->{_out} .= "</html>\n";

	return (1);
}

#####################################################################   
# include - object method
#
#####################################################################

sub include
{
	my ($self, $path) = @_;

	my $inc = Epiphyte::File->load($path, $self->{_level} + 1);

	if (!$inc->parse()) {
		return;
	}


	return (1);
}


#####################################################################   
# eval - object method
#
# Perform evaluation of a block of Perl.
#####################################################################   

sub eval
{
	my ($self, $block) = @_;
	my $output = '';

	my %PARAM = ();
	my %REQUEST = (
		method		=> $self->{_req}->method(),
		uri		=> $self->{_req}->uri(),
		remote_addr	=> '',
		user_agent	=> $self->{_req}->header('User-Agent'),
	);

	my $hpar = new HTTP::Request::Params($self->{_req});

	my $param = $hpar->params();
	foreach (keys(%$param)) {
		$PARAM{$_} = $param->{$_};
	}

	# Open a pipe and redirect standard output to that pipe for the
	# evaluation block so regular print statements can be used for
	# output.  This is suboptimal...need to find a better way to do this.
	pipe(ROUT, RIN);
	local *STDOUT = \*RIN;

	eval "$block";
	if ($@) {
		$self->error("failed to eval: $@");
	}
	close(RIN);
	$output .= $_ while (<ROUT>);
	close(ROUT);

	$output =~ s/\n\n/\n<br>\n/g;

	return ($output);
}

#####################################################################   
# error - object method
#
#####################################################################

sub error
{
	my ($self, @args) = @_;

	Epiphyte->log(LOG_ERR, $self->{_path}, '[', $self->{_lineno}, ']: ',
	    @args);
}

1;
